iT邦幫忙

2025 iThome 鐵人賽

DAY 3
0
Vue.js

新手也看得懂的 Vue.JS 前端系列 第 3

TypeScript 究竟是什麼呢?它為什麼重要? - Day 2

  • 分享至 

  • xImage
  •  

我們學習 Vue 的時候,應該常常都會聽到 TypeScript,這個章節它是非必要,但我覺得也是挺重要的一環,那就讓我娓娓道來吧!

為了更快速理解,也減少字數篇幅,以下將 TypeScript 簡稱為 TS、JavaScript 簡稱為 JS

TypeScript 是什麼呢?其背景介紹

TypeScript 是一個由微軟(MSFT)所領導開發的「強型別」(待會會介紹這是什麼)語言,他可以幫助 JavaScript 開發者更明瞭及有系統性地開發。TypeScript 在開發時期就會檢查到錯誤了,因此受到許多偏好規範性開發者的喜愛。TypeScript 會將 TS 檔案轉譯回 JS,使得瀏覽器能夠讀得懂,因此讀者可以將 TS 視為是建構在以 JS 之上的,用來彌補 JS 在型別檢查上的不足(JS會採用弱型別也是有原因的,本文將不贅述,有興趣的可以自行 Google「why javascript use weak-type」)。

因此讀者可以將 TS 視為:是為 JS 的強型別而生,提供靜態型別檢查。
OK,這裡就不再過多介紹,我們就直接進入正題吧!

TypeScript 語法介紹

其實 TS 的語法幾乎繼承自 JS,且在 JS 的基礎上再增加強型別語言該有的「保留字(keyword:是為一個語言的基本語法單字,有不可取代性,以下會做介紹)」。

保留字有非常多,包括但不限於「let、const、for、if、...」,這些語言本身所自帶的或保留的被稱作「保留字」。

變數結構

我們這裡就稍微示範一下 TS 的語法結構:

let value1: number = 100; // 將 100 賦值於 value1,其型別為「number(數字)」。
let value2: number = 200; // 將 200 賦值於 value2,其型別為「number(數字)」。

console.log(value1 + value2); // 將 value1 與 value2 相加,並顯示在終端機上,可以理解為 print。

那 JS 則是:

let value1 = 100; // 沒有強加型別
let value2 = 200; // 沒有強加型別

console.log(value1 + value2);

因此如果您 JS 這樣寫,也是能夠執行:

let value1 = 100; // 沒有型別
let value2 = "hello"; // 沒有型別

console.log(value1 + value2); // 100hello

但是 TS 就不行:

let value1: number = 100; // 數字型別
let value2: string = "hello"; // 字串型別

console.log(value1 + value2); // 無法相加,會報出錯誤:型別不相容

因此開發者如果亂傳一些值,JS會全盤接受,除非開發者自己手動判斷型別,否則會造成很多開發上的問題。因此透過 TS 一開始就設定好型別,不僅能夠讓開發者快速傳入,也降低錯誤的發生機率(因為一開始就已經檢查完了)。

陣列

陣列是一個很特別的東西,陣列可以把多個值放在同一個序列中。讀者可以將此想像成你在吃糖葫蘆,你一顆一顆買,那為何不買一整串?

定義陣列也很簡單:

let nums: number[] = [100, 200, 300];

console.log(nums[0]); // 第 0 個元素,即 100。
console.log(nums[1]); // 第 1 個元素,即 200。
console.log(nums[2]); // 第 2 個元素,即 300。

要注意的是,陣列都是從第 0 個元素開始算,長度為 n,則最後一個索引(index, idx)可以寫成「第 n - 1 個元素」。以上述為例,有三個元素,第一個元素為第零個索引,最後一個元素為第二個索引。

箭頭函式結構

我們可以看到,如果要將一個變數規定型別,則遵守著這個規則「變數: 型別 = 值」,函數也同樣如此(以 箭頭函式(arrow func)為例):

const add = (x: number, y: number): number => { // 定義一個箭頭函數 add(x, y),可傳入 x(型別為number)、y(型別為number)。
  return x + y; // 回傳 x + y,型別為 number。
}

console.log(add(100, 200)); // 將 100 及 200 傳入 add(x, y),輸出為 300。

函數則是遵守這個規則「函數名(傳入參數1: 型別1, 傳入參數2: 型別2, ..., 傳入參數n: 型別n): 傳出型別」。

傳統函式結構

傳統函式與箭頭函式大致相同:

function add(x: number, y: number): number { // 定義一個函數 add(x, y),可傳入 x(型別為number)、y(型別為number)。
  return x + y; // 回傳 x + y,型別為 number。
}

console.log(add(100, 200)); // 將 100 及 200 傳入 add(x, y),輸出為 300。

型別種類

上述已經舉例了 number,那我們來舉一反三,既然有數字,那會不會有字串呢?(廢話XD),那我們一起來看吧!

我們舉一些常用的就好,其他讀者可以再自行上 TypeScript 網站 - Everyday Types上學習。

型別 簡介
number 數字
string 字串
boolean 布林
null 空值
undefined 未定義值、未賦值
any 任何

前三個,相信讀者應該都能夠理解,number = 數字, string = 字串, boolean = 布林(真、假)。
比較特別需要說明的是後三個常用的:

  • null:表示「空值」特別,是針對「沒有值」。
  • undefined:表示「未定義」特別是針對「未賦值」。
  • any:可以是任何型別(盡量少用)。

null、undefined

其中 null 與 undefined 我知道讀者剛開始學習的時候常常會搞混,null 比較像是已經建立變數,被賦值為「沒有值」,而 undefined 比較像是已經建立變數,但未賦值。

文謅謅吧!?我們來跑跑看!

let x = null; // 已定義變數,有賦值,但為空值
let y; // 已定義變數,尚未賦值

console.log(x); // null(空值)
console.log(y); // undefined(未定義值)

any

相信讀者對這兩個型別有更加的認識了,那... any呢?如果你無法透過官方的型別來定義他,無非就是兩種方法:

  1. 自行寫型別
  2. 透過 any 暫時略過嚴格檢查(慎用及少用)

看不懂?直接上 code!

const readPhoto = (url: string): any => { // 定義讀取照片函數,傳入圖片網址(型別為 string 字串),傳出每一個像素(型別不知道,因此使用 any)。
  const pixel = {}; // 宣告 pixel 像素
  // ...(省略)
  return pixel;
}

readPhoto('圖片網址');

以這個例子來說,除非開發者自行定義 Photo Pixel 的型別,否則使用 any。但這個做法會造成,他可以傳出 number、string、boolean、undefined、null 等其他型別,會造成開發上的混亂。因此我們能夠盡量定義型別就定義,非必要盡量不使用 any。

型別別名(Type)

那該怎麼自己定義型別呢?我們可以利用保留字 type,直接上 code:

type userID = number | string; // 定義使用者ID型別可以接受「數字、字串」

const getUID = (uid: userID): string => { // 定義取得UID的函式,接受傳入 uid(型別為 userID),傳出字串
 return "U-" + uid // 將 U-{UID} 傳出
}

console.log(getUID("abc123")); // 傳入 abc123(string),傳出 U-abc123(string)。
console.log(getUID(456)); // 傳入 456(number),傳出 U-456(string)。

介面(Interface)

那如果開發者有很多個 type 呢?那就要使用 interface 了,可以表示一個「物件結構」。

type name = string; // 定義 name 可接受 字串
type uid = number | string; // 定義 uid 可接受 數字或字串 

interface userProps { // 定義使用者的參數
  name: name; // 使用者可以有 姓名,其型別為 name
  uid: uid; // 使用者可以有 uid,其型別為 uid
}

const getUID = (props: userProps): string => { // 定義取得UID的函式,接受傳入參數,傳出字串
 return "U-" + props.uid // 將 U-{UID} 傳出
}

const getName = (props: userProps): string => { // 定義取得姓名的函式,接受傳入參數,傳出字串
 return props.name // 將 姓名 傳出
}

console.log(getUID({name: "Andy", uid: "abc123"})); // 傳入 {姓名, uid}(此為 Object(物件)),傳出 U-uid(string)
console.log(getUID({name: "Lily", uid: 1000})); // 傳入 {姓名, uid}(此為 Object(物件)),傳出 U-uid(string)
console.log(getName({name: "John", uid: 1001})); // 傳入 {姓名, uid}(此為 Object(物件)),傳出 name(string)

注意一下,Object 也是型別的一種,很類似 JSON,但並不是。

至於 Props 是什麼?在第五篇會介紹到,本篇先不介紹,怕混亂,讀者先知道這個就行。

Type 與 Interface 的差別

如果讀者讀不懂,可以多讀幾次,大概就可以看出差別了。

type 比較講的是「單一變數」的型別,而 interface 比較講的是「多個變數」的型別。例如開發者可以定義「使用者」,使用者有姓名和UID。姓名的型別是字串、UID的型別可以是字串和數字,而加工處理後的UID是字串。

JS 與 TS 的差別

如果是 JS 的話,JS就會稍微複雜了。JS 它屬於弱型別,支持動態轉換,幾乎都是直接寫,如果沒有特別檢查的話,很常會發生 Error 或是 Bug。因此透過「強型別定義」,我們可以更好的開發。就以剛剛的例子舉例:

let value1 = 100; // 沒有強加型別
let value2 = "hello"; // 沒有強加型別

console.log(value1 + value2); // 100hello

讀者可能會很納悶,為什麼 100 可以跟字串加在一起?別懷疑XD,就是可以。至於要探討為什麼可以,其實就是「弱型別」所造成的,其實弱型別針對瀏覽器來說是更好的,因為不會網頁跑一跑就不能跑,更強調的是「可適配性」,以瀏覽器能夠兼容為主。而強型別是針對「開發階段」。那是不是就可以理解為什麼瀏覽器傾向吃JS,而開發傾向吃TS,正是因為雙方要的不同,使用者要的是方便,開發者要的是嚴謹。至於還有許多原因,歡迎自行查詢相關資料,本文就不再贅述。

可讀性

相信讀者從上面的範例來看,對 TS 已經有一個基礎的認識,正所謂「師傅引進門,修行在個人」,此教學重不在「複雜的專業」,而是在「讓你跨出第一步」。
讀者可以從 TS 的結構與語法來看,都可以看出 TS 是強型別,更容易去讀。再加上 TS 本身就已經有規範了,因此 code 並不會差到哪去。當然,一個差的開發者,不管用再規範的語言或是 formatter(語法格式化工具),都可以寫出「屎山代碼」XD。因此我們如果更需要去加強 coding style(程式碼風格),各位可以去讀 clean code(非業配) 或 相關探討程式碼風格和優化(optimize)的書籍或文章,本教學不會講到這一塊。

強型別 vs 弱型別

究竟強型別和弱型別差在哪?我在這邊再舉例一個可讀性的例子:

type 性別型別 = string
interface 八字 {
  性別: 性別型別
  年: number;
  月: number;
  日: number;
  時: number;
  分: number;
}
const 計算八字 = (props: 八字): string => {
  let 輕重;
  // ...省略(我就不寫了XD,因為我也不知道怎麼計算八字)
  return 輕重;
}

console.log(計算八字({
  性別: '男',
  年: 2000,
  月: 1,
  日: 1,
  時: 08,
  分: 0
})) // 傳入函數,輸出告訴你輕還是重

如果是 JavaScript 的話,則是:

const 計算八字 = (性別, 年, 月, 日, 時, 分) => {
  let 輕重;
  // ...省略(我就不寫了XD,因為我也不知道怎麼計算八字)
  return 輕重;
}

console.log(計算八字({
  性別: '男',
  年: 2000,
  月: 1,
  日: 1,
  時: 08,
  分: 0
})) // 傳入函數,輸出告訴你輕還是重

我們來思考一下如果用 JS 會發生什麼是?我性別貌似可以傳入數字,我年月日時分可以傳入字串,那在不判斷的情況下,是不是就非常容易造成錯誤或是 bug?且對可讀性也非常的差,因為開發者可以在 TS 上非常簡便的看出我該傳入什麼傳出什麼,對於多人開發來說,是再適合不過了。另外我們在大型專案多人開發,也不僅會使用 TS,更還會透過「單元測試」來測試數據是否正確或極端案例(edge cases)是否可以處理得當。但本教學不會特別說明更進階的單元測試,如果有興趣可以自己查資料學習。

這裡再補一句,其實 JS 算是動態型別,可以幫忙轉型,但這種方便相對的就會變成隱患。但這裡為了好理解,就稱其為弱型別吧。

TS 的缺點

說完了一堆優點,我們來看看缺點吧!其實不難理解,他的優點就是他的缺點,他高度可讀性,相對的就是需要寫更多「規範」,開發者需要規範型別。同樣的邏輯,在 JavaScript 開發者可以更少的 code 實現,但得到的就是更多的「不確定性及風險」,當然拉,你也可以全部都用 any 混過去XD,那你就沒有使用 TS 的必要了吧?但以長期來說,仍然會是強型別更有辦法維護。我也建議新手一開始就學習 TS,建立良好的觀念,等熟了再來寫 JS 吧,會避掉非常多坑洞的。

TypeScript 非必要但為什麼重要?

我這邊呢,就分三個來講:

  1. 奠定扎實的基礎:對於新手來講,處理型別可以讓你避掉非常多 JS 弱型別的錯誤和 bug。
  2. 多人協作的好處:對於團隊來說,與你協作的同事,他可以更快看出該資料類型什麼。
  3. 可長期維護:因為可讀性變高了,程式碼有一定的規範,因此長期維護成本一定是相較於 JS 來說,稍微降低了。

本文結尾

本篇還有非常多很有趣的 TS 保留字和結構沒講,例如泛型、繼承、斷言,以及非常多的型別沒提到。因為篇幅的關係,我就不再這裡一一贅述了,主要只是想讓讀者學習 TS,接下來的教學呢,為了讓新手更好理解,以及奠定更扎實的基礎,都會以 TS 為主。

我們可以看到使用 TS 益處多多,雖然他也有許多缺點,例如轉譯速度等,但我覺得再說下去,應該要講好久了,以後如果有機會可以再說到這些,我們就邊做邊學吧~

好了,本篇就到此為止,下一章我們終於可以來講講 Vue.JS 了,花了一整篇講 TS,算是先幫讀者惡補一下哈哈。下一章我們來介紹,本教學的第一個階段,《一起來安裝 Vue.JS 及 Vite 吧! - Day 3》。


上一篇
Vue.JS是什麼? - Day 1
系列文
新手也看得懂的 Vue.JS 前端3
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言